package u.hnx.admin.support.redis.config;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONException;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.Validate;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/*
创建人： 倔强的头发
创建时间：2024/6/22
功能描述: redis缓存管理类

*/
@Component
@ConditionalOnClass(StringRedisTemplate.class)
public record RedisCore(StringRedisTemplate stringRedisTemplate) {
    /**
     * String缓存对象
     *
     * @param key   字符串缓存的键值
     * @param value 字符串缓存的值
     */
    public void setCache(final String key, final String value) {
        set(key,value,null,null);
    }
    /**
     * 设置键值对到Redis，并可选地设置过期时间。
     *
     * @param key 键，不能为空。
     * @param value 值，不能为空。
     * @param timeout 过期时间，如果为null，则不设置过期时间。
     * @param timeUnit 时间单位，如果timeout为null，此参数无效。
     */
    private void set(final String key,final String value,Integer timeout,TimeUnit timeUnit){
        Validate.isTrue(StrUtil.isNotBlank(key), "键不能为空");
        Validate.isTrue(StrUtil.isNotBlank(value), "值不能为空");
        ValueOperations<String, String> stringStringValueOperations = stringRedisTemplate.opsForValue();
        try {
            if (timeout != null && timeUnit != null) {
                stringStringValueOperations.set(key, value, timeout, timeUnit);
            } else {
                stringStringValueOperations.set(key, value);
            }
        } catch (Exception e) {
            throw new NotImplementedException(e);
        }
    }
    /**
     * String缓存对象并设置过期时间
     *
     * @param key      缓存的键值
     * @param value    缓存的值
     * @param timeout  时间
     * @param timeUnit 时间颗粒度
     */
    public void setCache(final String key, final String value, final Integer timeout, final TimeUnit timeUnit) {
        set(key, value, timeout, timeUnit);
    }
    /**
     * 其它数据缓存对象
     *
     * @param key   缓存的键值
     * @param value 缓存的值
     */
    public void setCacheObject(final String key, Object value) {
        setCache(key,toJsonStr(value));
    }
    private String toJsonStr(Object value){
        Validate.isTrue(ObjectUtil.isNotNull(value),"输入值不能为空");
        try{
            return JSON.toJSONString(value);
        }
        catch (Exception ex){
            throw new RuntimeException("JSON序列化失败", ex);
        }
    }
    /**
     * 其它缓存对象并设置过期时间
     *
     * @param key      缓存的键值
     * @param value    缓存的值
     * @param timeout  时间
     * @param timeUnit 时间颗粒度
     */
    public void setCacheObject(final String key, final Object value, final Integer timeout, final TimeUnit timeUnit)  {
        setCache(key,toJsonStr(value),timeout,timeUnit);
    }
    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public String getCache(final String key) {
        try {
            String str = stringRedisTemplate.opsForValue().get(key);
            if (!StringUtils.hasLength(str)) {
                return null;
            }
            return removeDoubleQuotes(str);
        } catch (Exception e) {
            return null;
        }
    }
    /**
     * 移除字符串两端的双引号。
     *
     * @param str 待处理的字符串
     * @return 移除双引号后的字符串
     */
    private String removeDoubleQuotes(String str) {
        // 考虑到字符串可能在Redis中被序列化和反序列化，这里使用更健壮的方法来移除双引号
        return str.startsWith("\"") && str.endsWith("\"") ? str.substring(1, str.length() - 1) : str;
    }
    /**
     * incr方法用于对指定的键进行数值增加操作。
     *
     * @param key 键值，不能为null。
     * @param value 增加的数值，必须大于或等于0。
     * @return 增加后的数值。
     * @throws IllegalArgumentException 如果key为null或value小于0，则抛出此异常。
     */
    public long incr(String key, long value) {
        Validate.isTrue(StrUtil.isNotBlank(key), "键值参数不能为空");
        Validate.isTrue(value >= 0, "递增因子必须大于或等于0");
        return stringRedisTemplate.opsForValue().increment(key, value);
    }
    public <T> T getCacheObject(final String key,Class<T> type) {
        String str = stringRedisTemplate.opsForValue().get(key);
        if (!StringUtils.hasLength(str)) {
            return null;
        }
        try {
            return JSON.parseObject(str,type);
        }catch (JSONException ex){
            throw new RuntimeException("解析JSON失败,key:"+key+",原因："+ex.getMessage(),ex);
        }
        catch (Exception ex){
            throw new RuntimeException("解析JSON发生未知错误,key:"+key+",原因："+ex.getMessage(),ex);
        }

    }
    /**
     * 通过键值从缓存中获取指定类型的列表。
     * @param key 缓存中的键，用于检索值。
     * @param tClass 指定列表元素的类型，用于解析JSON数组。
     * @return 包含从缓存中检索到的列表的Optional。如果缓存中没有找到键或解析失败，则返回空的Optional。
     */
    public <T> Optional<List<T>> getCacheList(final String key, Class<T> tClass) {
        String str = stringRedisTemplate.opsForValue().get(key);
        if(!StringUtils.hasLength(str)){
            return  Optional.empty();
        }
        try {
            List<T> list = JSON.parseArray(str,tClass);
            return  Optional.of(list);
        }
        catch (Exception ex){
            throw new RuntimeException("JSON解析失败,key："+key+",原因："+ex.getMessage(),ex);
        }
    }
    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key) {
        if(StrUtil.isBlank(key)){
            return false;
        }
        try {
            return stringRedisTemplate.delete(key);
        } catch (Exception e) {
            return  false;
        }
    }
    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key) {
        Validate.isTrue(StrUtil.isNotBlank(key), "键值参数不能为空");
        return stringRedisTemplate.hasKey(key);
    }

    public Collection<String> keys(final String pattern) {
        Validate.isTrue(StrUtil.isNotBlank(pattern), "键值通配符不能为空");
        return stringRedisTemplate.keys(pattern);
    }
    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @param unit    时间单位
     * @return true=设置成功；false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit) {
        return stringRedisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获取有效时间
     *
     * @param key Redis键
     * @return 有效时间
     */
    public long getExpire(final String key) {
        return stringRedisTemplate.getExpire(key);
    }
}
